package lempay

import (
	"crypto/md5"
	"encoding/hex"
	"net"
	"sort"
	"strings"

	"gitee.com/ujq/gopay"
	"gitee.com/ujq/gopay/pkg/xhttp"
	"gitee.com/ujq/gopay/pkg/xlog"
)

type Client struct {
	BaseUrl   string //接口地址
	Pid       int    //商户ID
	SecretKey string //商户密钥

	ReturnUrl string //异步通知地址
	NotifyUrl string //跳转通知地址

	IsProd bool //是否是正式环境

	DebugSwitch gopay.DebugSwitch //调试开关，是否打印日志
	logger      xlog.XLogger      //日志
	hc          *xhttp.Client     //请求客户端

	SignType string //签名类型

	ClientIp string //客户端IP
}

// 初始化支付宝客户端
// 注意：如果使用支付宝公钥证书验签，请使用 client.SetCertSnByContent() 或 client.SetCertSnByPath() 设置 应用公钥证书、支付宝公钥证书、支付宝根证书
// appid：应用ID
// privateKey：应用私钥，支持PKCS1和PKCS8
// isProd：是否是正式环境，沙箱环境请选择新版沙箱应用。
func NewClient(pid int, secretKey string, isProd bool) (client *Client, err error) {
	if pid == 0 || secretKey == gopay.NULL {
		return nil, gopay.MissAlipayInitParamErr
	}

	logger := xlog.NewLogger()       //日志
	logger.SetLevel(xlog.DebugLevel) //日志
	client = &Client{

		Pid:       pid,       //商户ID
		SecretKey: secretKey, //商户密钥

		IsProd: isProd, //是否是正式环境

		DebugSwitch: gopay.DebugOff,    //调试开关，是否打印日志
		logger:      logger,            //日志
		hc:          xhttp.NewClient(), //请求客户端

		SignType: MD5,            //签名类型
		ClientIp: GetLocalIPv4(), //客户端IP

	}
	return client, nil
}
func GetLocalIPv4() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return ""
	}
	for _, address := range addrs {
		// 检查 ip 是否 link-local address
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String()
			}
		}
	}
	return ""
}

// SetBodySize 设置http response body size(MB)
func (c *Client) SetBodySize(sizeMB int) {
	if sizeMB > 0 {
		c.hc.SetBodySize(sizeMB)
	}
}

// SetHttpClient 设置自定义的xhttp.Client
func (c *Client) SetHttpClient(client *xhttp.Client) {
	if client != nil {
		c.hc = client
	}
}

func (c *Client) SetLogger(logger xlog.XLogger) {
	if logger != nil {
		c.logger = logger
	}
}

// RequestParam 获取完整请求参数包含签名
func (c *Client) RequestParam(bm gopay.BodyMap) (string, error) {

	if bm == nil {
		return "", gopay.BodyMapNilErr
	}
	// check public parameter
	c.checkPublicParam(bm)

	// check sign
	if bm.GetString("sign") == "" {
		sign := c.GenerateSign(bm)
		bm.Set("sign", sign)
	}

	if c.DebugSwitch == gopay.DebugOn {
		c.logger.Debugf("获取完整请求参数包含签名: %s", bm.JsonBody())
	}
	return bm.EncodeURLParams(), nil
}

// 公共参数检查
func (c *Client) checkPublicParam(bm gopay.BodyMap) {

	if bm.GetString("pid") == "" && c.Pid != 0 {
		bm.Set("pid", c.Pid)
	}
	if bm.GetString("sign_type") == "" && c.SignType != gopay.NULL {
		bm.Set("sign_type", c.SignType)
	}
	if bm.GetString("notify_url") == "" && c.NotifyUrl != gopay.NULL {
		bm.Set("notify_url", c.NotifyUrl)
	}
	if bm.GetString("return_url") == "" && c.ReturnUrl != gopay.NULL {
		bm.Set("return_url", c.ReturnUrl)
	}

}

// 1. 签名算法实现 首先，你需要实现一个签名算法来生成 MD5 签名。
func (c *Client) GenerateSign(bm gopay.BodyMap) string {

	//----排序----
	keys := make([]string, 0, len(bm))
	for k := range bm {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	//----排序----

	// 构建签名字符串
	var signStrBuilder strings.Builder
	for _, key := range keys {
		value := bm.GetString(key)
		if key != "sign" && key != "sign_type" && value != "" {
			signStrBuilder.WriteString(key)
			signStrBuilder.WriteString("=")
			signStrBuilder.WriteString(value)
			signStrBuilder.WriteString("&")
		}
	}
	signStr := signStrBuilder.String()
	signStr = strings.TrimRight(signStr, "&") //移除最后1个&符号
	signStr += c.SecretKey

	h := md5.New()
	h.Write([]byte(signStr))
	return hex.EncodeToString(h.Sum(nil))
}

// 公共参数处理
func (c *Client) pubParamsHandle(bm gopay.BodyMap) (param string, err error) {
	pubBody := make(gopay.BodyMap)
	pubBody.Set("pid", c.Pid).
		Set("sign_type", c.SignType)

	if bm != nil {
		// return_url
		if c.ReturnUrl != gopay.NULL {
			pubBody.Set("return_url", c.ReturnUrl)
		}
		if returnUrl := bm.GetString("return_url"); returnUrl != gopay.NULL {
			pubBody.Set("return_url", returnUrl)
		}

		// notify_url
		if c.NotifyUrl != gopay.NULL {
			pubBody.Set("notify_url", c.NotifyUrl)
		}
		if notifyUrl := bm.GetString("notify_url"); notifyUrl != gopay.NULL {
			pubBody.Set("notify_url", notifyUrl)
		}

	}

	// sign
	sign := c.GenerateSign(pubBody)
	pubBody.Set("sign", sign)

	if c.DebugSwitch == gopay.DebugOn {
		c.logger.Debugf("公共参数处理: %s", pubBody.JsonBody())
	}
	param = pubBody.EncodeURLParams()
	return
}
